package dao.jpa;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;

import vo.BaseEntity;
import dao.GenericDao;

/**
 * Implementation of GenericDaoImpl that uses JPA
 * 
 * Notice that the finder methods assume that the named queries are declared
 * somewhere. THe most logical place is at the declaration level in the class
 * itself
 * 
 * @author quirozca
 * 
 * @param <T>
 *            Class to be persisted
 * @param <PK>
 *            Primary key class
 */
@SuppressWarnings("unchecked")
public class GenericDaoJpaImpl<T extends BaseEntity, PK extends Serializable> implements GenericDao<T, PK> {
	private Class<T> type;

	@PersistenceContext
	protected EntityManager em;

	public GenericDaoJpaImpl(Class<T> type) {
		this.type = type;
	}

	public PK create(T newInstance) {
		em.persist(newInstance);
		return (PK) newInstance.getId();
	}

	public T readReference(PK id) {
		return em.getReference(type, id);
	}

	public T read(PK id) {
		T t;
		try {
			t = em.find(type, id);
		} catch (JpaObjectRetrievalFailureException e) {
			return null;
		}
		return t;
	}

	public void update(T transientObject) {
		em.merge(transientObject);
	}

	public void delete(T persistentObject) {
		em.remove(persistentObject);
	}

	protected List<T> executeFinder(String queryName, Object... queryArgs) {
		Query query = em.createNamedQuery(queryName);
		if (queryArgs != null) {
			int i = 0;
			for (Object arg : queryArgs) {
				query.setParameter(++i, arg);
			}
		}
		return query.getResultList();
	}

	protected List<T> executeFinderPage(String queryName, int start, int pageSize, Object... queryArgs) {
		Query query = em.createNamedQuery(queryName);
		query.setFirstResult(start);
		query.setMaxResults(pageSize);
		if (queryArgs != null) {
			int i = 0;
			for (Object arg : queryArgs) {
				query.setParameter(++i, arg);
			}
		}
		return query.getResultList();
	}

	protected long executeFinderCount(String queryName, Object... queryArgs) {
		Query query = em.createNamedQuery(queryName);
		if (queryArgs != null) {
			int i = 0;
			for (Object arg : queryArgs) {
				query.setParameter(++i, arg);
			}
		}
		return (Long) query.getSingleResult();
	}

	protected List<T> executeFinder(String queryName, Map<String, ? extends Object> params) {
		Query query = em.createNamedQuery(queryName);
		for (Entry<String, ? extends Object> param : params.entrySet()) {
			query.setParameter(param.getKey(), param.getValue());
		}
		return query.getResultList();
	}

	protected T executeSingleFinder(String queryName, Object... queryArgs) {
		List<T> foundEntities = executeFinder(queryName, queryArgs);
		if (foundEntities.isEmpty()) {
			return null;
		} else {
			T t = foundEntities.get(0);
			return t;
		}
	}

	protected T executeSingleFinder(String queryName, Map<String, ? extends Object> params) {
		Query query = em.createNamedQuery(queryName);
		for (Entry<String, ? extends Object> param : params.entrySet()) {
			query.setParameter(param.getKey(), param.getValue());
		}
		List<T> foundEntities = query.getResultList();

		if (foundEntities.isEmpty()) {
			return null;
		} else {
			T t = foundEntities.get(0);
			return t;
		}
	}
}
